Published on
·

코드 수정이 쉬운 프론트엔드 아키텍처를 찾아서

프론트엔드 개발에서 아키텍처는 자유로운 편입니다. React라는 핵심 라이브러리가 프레임워크가 아니기도 하고, Next.js도 백엔드 프레임워크들과 비교하면 거의 express 수준의 자유도라고 생각합니다. 특히 상태 관리와 컴포넌트 분리는 프론트엔드 개발자에게 맡겨진 가장 어려운 고민 중 하나입니다.

공개된 좋은 아키텍처를 참고하려고 해도, 대부분의 기업에서 사용하는 큰 규모의 클라이언트 어플리케이션 코드는 비공개이며, 오픈소스로 공개된 코드들도 주로 비즈니스 로직을 덜어내어 추상화한 컴포넌트 라이브러리 위주입니다. 클라이언트 상태 관리 라이브러리인 zustandjotai, redux에서 Flux 패턴을 어떻게 활용하여 best practice를 만들 수 있는지에 대한 구체적인 지침은 문서에서도 찾기 어려웠습니다.

이 글은 좋은 프론트엔트 아키텍처에 대한 지식을 공유하는 글이 아니라, 제가 진행한 프로젝트에 적합한 아키텍처를 탐색하는 과정에서 겪은 여러 고민들에 대해 다루는 글입니다.

첫 구조

프로젝트 초기에 저희는 페이지 단위로 모든 상태와 액션을 관리하는 hook을 구현하여 컴포넌트에 주입하는 방식을 선택했습니다.

이 접근법은 비즈니스 로직뷰 로직의 분리를 가능하게 하며, 컴포넌트의 독립성재사용성을 높이는 장점을 가졌습니다. 하지만 서비스의 주요 기능이 복잡해짐에 따라, 페이지 단위의 hook이 과도하게 커지며 관리가 어려워졌습니다. 또한, 깊은 prop drilling이 코드 수정과 유지 보수에 어려움을 가중시켰습니다.

재사용성에 대한 고민

서비스의 특성 상, 컴포넌트의 재사용 빈도가 낮았습니다. 대부분의 경우, 버튼이나 입력 필드 같은 기본 컴포넌트만 재사용성이 중요했고 나머지는 그렇지 않았습니다. 거의 재사용하지 않는 컴포넌트의 재사용성을 고려하기 위해 너무 많은 고민을 해야 하는게 과연 좋은 아키텍처인가 생각하기 시작했습니다. 결국, 컴포넌트의 재사용성과 독립성에 고집하기보다는, 로직을 명확하게 분리하고 자주 변하는 요구사항에 유연하게 대응할 수 있는 코드 작성에 집중하기로 했습니다.

두번째 구조

상태 관리 라이브러리 도입

클라이언트 상태 관리를 개선하기 위해 zustand를 도입했습니다. 여전히 클라이언트 상태가 서버 상태에 의존하거나 부수 효과를 발생시킬 수 있기 때문에, 비즈니스 로직을 처리하는 hook은 일단 유지하기로 결정했습니다.

store -> hook -> component

Hook은 스토어의 상태를 가져오며 다양한 fetch 함수들을 포함하는 핸들러를 구성하여, 이를 상태 state행동 actions으로 나누어 컴포넌트에 전달합니다. 각각의 hook은 특정 관심사에 따라 분리되어 있어, 컴포넌트는 필요한 상태와 행동을 해당 hook을 통해 가져와 사용할 수 있습니다. 이러한 구조 덕분에, 컴포넌트 간 prop을 깊게 전달하는 prop drilling 문제가 완전히 해결됩니다.

그러나 이 방식을 적용했을 때, hook 내에서 불필요한 렌더링과 side effect가 발생할 수 있습니다. 때문에 이를 방지하기 위해 hook을 사용할 때 prop으로 렌더링 여부에 대한 값을 넘겨줘야 했고, hook 내에 분기를 통해 렌더링을 제어해야 했습니다.

최종 구조

결국, 도메인별 공용 hook을 포기하고 렌더링과 side effect에 대한 자유를 얻기로 했습니다. 대신 각 컴포넌트는 hook을 갖게 되었고, 이 hook들은 zustand를 통해 관리되는 도메인별 상태 스토어에 접근합니다.

장점

  • Prop Drilling의 완전한 제거: 각 컴포넌트는 자체적인 hook을 가지고 있으며, 이 hook들이 스토어로부터 직접 상태를 가져오기 때문에, 상위 컴포넌트로부터 props를 전달받을 필요성이 사라집니다.
  • 코드 수정의 용이성: 스토어가 독립적인 상태를 유지하며, 컴포넌트나 hook 사이에 직접적인 연관이 없기 때문에, 코드 변경 시 부수 효과가 발생할 가능성이 낮습니다.
  • 가독성의 향상: 비즈니스 로직과 뷰 로직이 hook과 컴포넌트로 명확히 분리되어 있으며, hook에서 컴포넌트로 전달되는 로직들이 상태state와 행동actions로 구분되어 있어, 전체적인 코드의 가독성이 높아집니다.
  • Hook의 크기 축소: hook이 재사용보다는 각 컴포넌트에 특화되어 디자인됨으로써, 각 hook의 크기가 상대적으로 작아지는 결과를 가져옵니다.

단점

  • 소수의 공용 컴포넌트와 훅을 제외한 나머지 컴포넌트와 훅은 재사용이 불가능합니다.
  • 테스트 해야 할 코드가 많아지고, 난이도가 증가합니다.

결론

아키텍처는 프로젝트의 요구 사항과 팀의 선호에 따라 유동적으로 변화해야 합니다. 모든면에서 좋고 모든면에서 나쁜 아키텍처는 없다고 생각합니다. 지금 프로젝트와 팀에 어떤 요소를 우선적으로 생각할지 정하고, 트레이드오프를 계산하면 더 적절한 아키텍처를 찾을 것 같습니다.